package lumis.doui.control.itempicker;

import lumis.doui.control.ControlContainer;
import lumis.doui.control.ControlException;
import lumis.doui.control.DataBoundControl;
import lumis.doui.control.IClientSideReadableControl;
import lumis.doui.control.IControl;
import lumis.doui.processaction.IProcessActionHandler;
import lumis.doui.render.IRenderable;
import lumis.doui.render.IRenderer;
import lumis.doui.source.ISourceData;
import lumis.doui.source.TabularData;
import lumis.doui.source.TabularSource;
import lumis.doui.source.field.ITabularSourceField;
import lumis.portal.PortalException;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.servicecontainer.ServiceContainerUrl;
import lumis.portal.serviceinstance.ServiceInstanceConfig;
import lumis.portal.serviceinterface.IServiceInterfaceUrl;
import lumis.portal.transaction.PortalTransactionFactory;
import lumis.util.XmlUtil;
import lumis.util.converter.AbstractConverter;
import lumis.util.converter.IConverter;
import lumis.util.converter.UnsupportedConversionTypeException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Locale;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

/**
 * Multiple item picker control.
 * <p>
 * Allows the end user to add and remove multiple items.
 * <p>
 * This control contains four significant properties. The id of the field that each of 
 * the items in its list represents, the id of that field that represents a user friendly 
 * name for the field that it represents, the primary key of each item, and finally an 
 * interface that is responsible for searching and selecting the items.
 * <p>
 * The control renders a readonly user friendly name of each item with clear icon beside
 * it.  When the user selects the selection icon the specified search interface is popped
 * passing the javascript function name that should be called to include the identifier and
 * friendly name of the item.
 * <p>
 * <br/>
 * Example use:
 * <xmp>
 * <control:lum_multiItemPicker id="To" sourceId="messageUser" itemKeyFieldId="userId" itemNameFieldId="userName">
 * 	<searchInterfaceId>selectUser</searchInterfaceId>
 * </control:lum_multiItemPicker>
 * </xmp>
 * <br/>
 *
 * xml data available for xsl rendering:
 * <xmp>
 * <control dataId="userId" displayDataId="userName" id="userId.userId" sourceId="default" type="lum_itemPicker">
 * 	<searchInterfaceId>lumis.service.sample.selectusingsearch.selectUser</searchInterfaceId>
 * 	<control id="8A488A0F158A409701158A42522F0095" type="lum_requiredValueValidator" />
 * 	<data>
 * 		<item>
 * 			<id>00000000D00000000000000000000004</id>
 * 			<displayString>LumisPageCacheUser</displayString>
 * 		</item>
 * 		<openPopupScript><!-- javascript for opening a pop to select an item --></openPopupScript>
 * 		<selectItemFunction><!-- javascript function that is called by the pop to set the id and display name of the item selected --></selectItemFunction>
 * 		<clearItemScript><!-- javascript to clear the id and name selected --></clearItemScript>
 * 	</data>
 * </control>
 * </xmp>
 * 
 * <control id="To" itemKeyFieldId="userId" itemNameFieldId="userName" sourceId="messageUser" type="lum_multiItemPicker">
 * 	<searchInterfaceId>selectUser</searchInterfaceId> 
 * 	<data>
 *		<rows /> 
 * 		<openPopupScript><!-- javascript for opening a pop to select an item --></openPopupScript> 
 * 		<includeItemFunctionName><!-- javascript function that is called by the pop to include the id and display name of the item selected --></includeItemFunctionName> 
 * 		<formName>Form_8A488A0F15D262380115D2C1D8AC0327</formName> 
 * 		<formattedId>Form_8A488A0F15D262380115D2C1D8AC0327_To</formattedId> 
 * 	</data>
 * </control>
 * 
 * @version $Revision: 11835 $ $Date: 2010-08-05 16:29:01 -0300 (Thu, 05 Aug 2010) $
 * @since 4.1.0
 */
public class MultiItemPickerControl extends DataBoundControl<TabularSource<?>> implements IClientSideReadableControl
{
	private static class RowData
	{
		String primaryKeyId;
		String itemId;
		String itemName;
	}

	private static class MultipleItemData implements IRenderable
	{
		public Collection<RowData> dataRows = new ArrayList<RowData>();

		public String getDisplayString(IRenderer renderer) throws PortalException
		{
			return null;
		}

		public String getRenderData(IRenderer renderer) throws PortalException
		{
			StringBuilder dataRowsXmlString = new StringBuilder();
			for(RowData rowData : dataRows)
			{
				dataRowsXmlString.append("<row>");
				dataRowsXmlString.append("<pk>"+XmlUtil.encodeXml(rowData.primaryKeyId)+"</pk>");
				dataRowsXmlString.append("<itemId>"+XmlUtil.encodeXml(rowData.itemId)+"</itemId>");
				dataRowsXmlString.append("<itemName>"+XmlUtil.encodeXml(rowData.itemName)+"</itemName>");
				dataRowsXmlString.append("</row>");
			}
			
			return "<rows>"+dataRowsXmlString+"</rows>";
		}
	}
	
	private static class MultiItemDataConverter extends AbstractConverter
	{
		@SuppressWarnings("unchecked")
		@Override
		protected <T> T doConversion(Class<T> type, Object value, Locale locale, String pattern) throws UnsupportedConversionTypeException
		{
			if(type.isAssignableFrom(String.class))
			{
				return (T)value.toString();
			}
			else if(type.isAssignableFrom(MultipleItemData.class))
			{
				try
				{
					MultipleItemData newValue = new MultipleItemData();
					String requestRowsXmlString = value.toString();
					if(requestRowsXmlString != null && requestRowsXmlString.length() > 0)
					{
						requestRowsXmlString = XmlUtil.XML_HEADER+"<rows>"+requestRowsXmlString+"</rows>";
						Document requestRowsDocument = XmlUtil.getDocument(requestRowsXmlString);
						
						Node[] rowNodes = XmlUtil.selectNodes("rows/row", requestRowsDocument);
						
						for(Node rowNode : rowNodes)
						{
							RowData rowData = new RowData();
							rowData.primaryKeyId = XmlUtil.readNodeString("pk", rowNode);
							rowData.itemId = XmlUtil.readNodeString("itemId", rowNode);
							rowData.itemName = XmlUtil.readNodeString("itemName", rowNode);
							
							newValue.dataRows.add(rowData);
						}
					}
					
					return (T)newValue;
				}
				catch(PortalException e)
				{
					throw new RuntimeException(e);
				}
			}
			else
			{
				throw new UnsupportedConversionTypeException("type: "+type);
			}

		}
		
	}
	
	private String primaryKeyFieldId = null;
	private String itemKeyFieldId = null;
	private String itemNameFieldId = null;
	private String searchInterfaceId = null;
	private Collection<String> rowsDeleted = new ArrayList<String>();
	
	@Override
	protected Class<?> getValueClass() throws PortalException
	{
		return MultipleItemData.class;
	}
	
	@Override
	protected IConverter createConverter() throws PortalException
	{
		return new MultiItemDataConverter();
	}

	public void init(Node controlDefinitionNode, ControlContainer controlContainer, IControl parentControl) throws PortalException
	{
		super.init(controlDefinitionNode, controlContainer, parentControl);

		setPrimaryKeyFieldId(XmlUtil.readAttributeString("primaryKeyFieldId", controlDefinitionNode, getPrimaryKeyFieldId()));
		setItemKeyFieldId(XmlUtil.readAttributeString("itemKeyFieldId", controlDefinitionNode, getItemKeyFieldId()));
		setItemNameFieldId(XmlUtil.readAttributeString("itemNameFieldId", controlDefinitionNode, getItemNameFieldId()));
		setSearchInterfaceId(XmlUtil.readNodeString("searchInterfaceId", controlDefinitionNode));
		
		if(getPrimaryKeyFieldId() == null)
		{
			for(ITabularSourceField field : getSource().getFields())
			{
				if(field.isPrimaryKey())
				{
					setPrimaryKeyFieldId(field.getId());
					break;
				}
			}
		}
		
		if(getPrimaryKeyFieldId() == null)
			throw new ControlException("primaryKeyFieldId attribute not specified and could not be detected");
		
		if(getItemKeyFieldId() == null)
			throw new ControlException("itemKeyFieldId attribute not specified");
		
		if(getItemNameFieldId() == null)
			throw new ControlException("itemNameFieldId attribute not specified");
	}
	
	@Override
	protected void setValueFromSource(TabularSource<?> source) throws PortalException
	{
		setReload(false);

		if (source == null)
			return;

		MultipleItemData newValue = new MultipleItemData();
		
		TabularData tabularData = source.getData();
		if(tabularData.getRows().size() > 0)
		{
			newValue.dataRows.clear();
			
			for(ISourceData sourceRowData : tabularData.getRows())
			{
				RowData rowData = new RowData();
				rowData.primaryKeyId = sourceRowData.get(getPrimaryKeyFieldId(), String.class);
				rowData.itemId = sourceRowData.get(getItemKeyFieldId(), String.class);
				rowData.itemName = sourceRowData.get(getItemNameFieldId(), String.class);
				
				newValue.dataRows.add(rowData);
			}
		}
		
		setValue(newValue);
	}
	
	/**
	 * Loads the number of rows and index of row to delete if exists.
	 */
	@Override
	public void loadFromRequest() throws PortalException
	{
		super.loadFromRequest();
		
		String rowsDeletedParam = douiContext.getRequest().getParameter(getId() + ".rowsDeleted");
		if (rowsDeletedParam != null)
			for(String rowDeletedParam : rowsDeletedParam.split(";"))
				rowsDeleted.add(rowDeletedParam);
		
		if (getValue() == null)
		{
			// use an empty MultipleItemData as value if no value is set from request
			setValue(new MultipleItemData());
		}
	}

	@Override
	public void setRenderData() throws PortalException
	{
		super.setRenderData();

		Node dataNode = XmlUtil.selectSingleNode("data", controlDefinitionNode);
		if(dataNode == null)
			dataNode = 	XmlUtil.addNode(controlDefinitionNode, "data");

		StringBuilder rowsDeletedString = new StringBuilder();
		for (String rowDeleted: rowsDeleted)
		{
			if(rowsDeletedString.length() > 0)
				rowsDeletedString.append(";");
			rowsDeletedString.append(rowDeleted);
		}
		
		if(rowsDeletedString.length() > 0)
			XmlUtil.addTextNode(dataNode, "rowsDeleted", rowsDeletedString.toString());
		
		XmlUtil.addNodeFromXmlString(dataNode, getValue(MultipleItemData.class).getRenderData(this));
		
		String formName = controlContainer.getFormName();

		IServiceInterfaceUrl popupUrl = getPopupUrl();
		XmlUtil.addTextNode(dataNode, "openPopupScript", ((ServiceContainerUrl) popupUrl).getWindowOpen(douiContext.getTransaction()) + ";");
		
		StringBuilder functionStringName = new StringBuilder();
		functionStringName.append("include_" + formName + "_" + getId().replace(".", ""));
		
		XmlUtil.addTextNode(dataNode, "includeItemFunctionName", functionStringName.toString());
		XmlUtil.addTextNode(dataNode, "formName", formName);
		XmlUtil.addTextNode(dataNode, "formattedId", formName+"_"+getId().replace(".", ""));
	}

	protected IServiceInterfaceUrl getPopupUrl() throws PortalException
	{
		IServiceInterfaceUrl popupUrl = douiContext.getResponse().createRenderURL();

		String searchInterfaceId = getSearchInterfaceId();
		boolean requiresServiceInstance = ManagerFactory.getServiceInterfaceManager().getServiceInterfaceConfig(searchInterfaceId,
				PortalTransactionFactory.getCurrentTransaction()).isServiceInstanceRequired();

		if (requiresServiceInstance)
		{
			String serviceInstanceId = XmlUtil.readAttributeOrNodeString(controlDefinitionNode, "serviceInstanceId");
			boolean addedServiceInstanceId = false;
			if (serviceInstanceId != null)
			{
				popupUrl.setRunTimeServiceInstanceId(serviceInstanceId);
				addedServiceInstanceId = true;
			}
			if (!addedServiceInstanceId)
			{
				ServiceInstanceConfig serviceInstanceConfig = douiContext.getRequest().getServiceInstanceConfig();
				if (serviceInstanceConfig != null)
				{
					popupUrl.setRunTimeServiceInstanceId(serviceInstanceConfig.getId());
					addedServiceInstanceId = true;
				}
			}
			if (!addedServiceInstanceId)
				throw new ControlException("STR_SERVICE_INSTANCE_NOT_FOUND");
		}

		popupUrl.setRunTimeServiceInterfaceId(getSearchInterfaceId());
		popupUrl.setParameter("callbackFunction", "include_" + controlContainer.getFormName() + "_" + getId().replace(".", ""));

		return popupUrl;
	}

	@Override
	public void setProcessActionHandlerParameters(IProcessActionHandler processActionHandler) throws PortalException
	{
		int count = 0;
		
		Collection<RowData> dataRows = getValue(MultipleItemData.class).dataRows;
		processActionHandler.setParameter(getId() + ".numberOfRows", dataRows.size());
		processActionHandler.setParameter(getId() + ".rowsDeleted", this.rowsDeleted);
		for(RowData rowData : dataRows)
		{
			count++;
			
			processActionHandler.setParameter(Integer.toString(count)+"."+getItemKeyFieldId(), rowData.itemId);
			
			if(rowData.primaryKeyId != null && rowData.primaryKeyId.length() > 0)
				processActionHandler.setParameter(Integer.toString(count)+"."+getPrimaryKeyFieldId(), rowData.primaryKeyId);
		}
	}

	public String getItemKeyFieldId()
	{
		return itemKeyFieldId;
	}

	public void setItemKeyFieldId(String itemKeyFieldId)
	{
		this.itemKeyFieldId = itemKeyFieldId;
	}

	public String getPrimaryKeyFieldId()
	{
		return primaryKeyFieldId;
	}

	public void setPrimaryKeyFieldId(String primaryKeyFieldId)
	{
		this.primaryKeyFieldId = primaryKeyFieldId;
	}

	public String getItemNameFieldId()
	{
		return itemNameFieldId;
	}

	public void setItemNameFieldId(String itemNameFieldId)
	{
		this.itemNameFieldId = itemNameFieldId;
	}

	public String getSearchInterfaceId()
	{
		return searchInterfaceId;
	}

	public void setSearchInterfaceId(String searchInterfaceId)
	{
		if(searchInterfaceId.contains("."))
		{
			this.searchInterfaceId = searchInterfaceId;
		}
		else
		{
			this.searchInterfaceId = this.douiContext.getRequest().getServiceConfig().getId()+"."+searchInterfaceId;
		}
	}
}